package com.santu.com.util;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author luoyaoqi
 * @createTime 2022/7/8
 * @description
 */
@Component
@Aspect
public class ApiLockAspectPoint {

    private static final Logger log = LoggerFactory.getLogger(ApiLockAspectPoint.class);

    private static final String EXCLUDE_JAVA_BEAN_FILED = "serialVersionUID";

    private Map<Class<? extends ApiLockKeyGenerateStrategy>, ApiLockKeyGenerateStrategy> apiLockKeyGenerateStrategyMap = new ConcurrentHashMap<>();

    @Autowired
    @Qualifier("apiRedissonClient")
    private RedissonClient apiRedissonClient;

    @Pointcut("@annotation(com.santu.com.util.ApiLock)")
    public void execute() {
    }

    @Around("execute()")
    public Object around(ProceedingJoinPoint thisJoinPoint) throws Throwable {
        //取方法上的注解，为空则不处理
        MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
        ApiLock annotation = signature.getMethod().getAnnotation(ApiLock.class);
        if (annotation == null) {
            thisJoinPoint.proceed();
        }

        //拿方法中所有非空的参数，如果参数为空值或null则不处理
        String methodName = thisJoinPoint.getSignature().getName();
        Map<String, Object> methodNotNullArgsMap = getMethodNotNullArgsMap(thisJoinPoint, annotation);
        if (null == methodNotNullArgsMap || methodNotNullArgsMap.isEmpty()) {
            log.error("[{}] ==> there are no non-null arguments!", methodName);
            return thisJoinPoint.proceed();
        }
        //根据指定策略拿lockKey
        Class<? extends ApiLockKeyGenerateStrategy> generateStrategy = annotation.generateStrategy();
        ApiLockKeyGenerateStrategy keyGenerator;
        if (apiLockKeyGenerateStrategyMap.containsKey(generateStrategy)) {
            keyGenerator = apiLockKeyGenerateStrategyMap.get(generateStrategy);
        } else {
            keyGenerator = generateStrategy.newInstance();
            apiLockKeyGenerateStrategyMap.put(generateStrategy, keyGenerator);
        }
        //生成key
        String prefix = keyGenerator.getKeyPrefix(thisJoinPoint);
        String lockKey = keyGenerator.generateKey(prefix, methodNotNullArgsMap);
        RLock lock = null;
        //加锁，执行方法
        try {
            lock = apiRedissonClient.getLock(lockKey);
            boolean flag = lock.tryLock(annotation.waitMills(), annotation.expireMills(), annotation.timeunit());
            if (!flag) {
                throw new RuntimeException("操作过于频繁");
            }
            return thisJoinPoint.proceed();
        } finally {
            // 最后一定要解锁
            if (lock.isLocked()) {
                lock.unlock();
                log.info("[{}]==> api lock process completed", methodName);
            }
        }
    }


    /**
     * 获取方法中的参数，过滤参数为空的参数
     *
     * @param joinPoint
     * @param annotation
     * @return
     */
    private Map<String, Object> getMethodNotNullArgsMap(ProceedingJoinPoint joinPoint, ApiLock annotation) {
        Object[] args = joinPoint.getArgs();
        DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
        if (parameterNames == null) {
            return null;
        }

        //将所有参数丢到map中
        HashMap<String, Object> resultParamMap = new HashMap<>();

        for (int i = 0; i < parameterNames.length; i++) {
            Object obj = args[i];
            if (obj == null) {
                continue;
            }
            //如果参数类型是HttpServletRequest,判断一下是否需获取attr、header
            if (obj instanceof HttpServletRequest) {
                HttpServletRequest request = (HttpServletRequest) obj;
                checkAndFillReqAttrIfNecessary(resultParamMap, annotation, request);
                checkAndFillHeaderParamsIfNecessary(resultParamMap, annotation, request);
            }
            resultParamMap.put(parameterNames[i], obj);
        }
        if (resultParamMap.isEmpty()) {
            return null;
        }

        //考虑参数可能封装多层的情况，以及其中可能出现Entry的key或value为空值，需要remove掉这些Entry
        //防止空参数导致生成重复的key，从而导致升级成一把全局锁
        RemoveNullEntryUtil.removeNullEntry(resultParamMap);
        return resultParamMap;
    }

    private void checkAndFillHeaderParamsIfNecessary(HashMap<String, Object> resultParamMap, ApiLock annotation, HttpServletRequest request) {
        if (annotation.requiredHeaders() != null) {
            String[] headerArr = annotation.requiredHeaders();
            for (String header : headerArr) {
                String headerValue = request.getHeader(header);
                if (StringUtils.isEmpty(headerValue)) {
                    log.error("==> header:{} not exist", header);
                    continue;
                }
                resultParamMap.put(header, headerValue);
            }
        }
    }

    private void checkAndFillReqAttrIfNecessary(HashMap<String, Object> resultParamMap, ApiLock annotation, HttpServletRequest request) {
        if (annotation.requiredRequestAttrs() != null) {
            String[] requestAttrs = annotation.requiredRequestAttrs();
            for (String attr : requestAttrs) {
                Object val = request.getAttribute(attr);
                if (val == null) {
                    log.error("==> request attr:{} not exist", attr);
                    continue;
                }
                resultParamMap.put(attr, val);
            }
        }
    }


    static class RemoveNullEntryUtil {


        /**
         * 移除map中空key或者value空值
         *
         * @param map
         */
        private static void removeNullEntry(Map<String, Object> map) {
            removeNullOrEmptyKey(map);
            removeNullEmptyValue(map);
        }

        private static void removeNullOrEmptyKey(Map<String, Object> map) {
            Set<String> set = map.keySet();
            Iterator<String> iterator = set.iterator();
            while (iterator.hasNext()) {
                String next = iterator.next();
                if (StringUtils.isEmpty(next)) {
                    iterator.remove();
                }
            }
        }

        private static void removeNullEmptyValue(Map<String, Object> map) {
            Set<String> set = map.keySet();
            Iterator<String> iterator = set.iterator();
            while (iterator.hasNext()) {
                String key = iterator.next();
                Object value = map.get(key);
                if (isObjectNullOrEmpty(value)) {
                    iterator.remove();
                }

            }
        }

        @SuppressWarnings("unchecked")
        private static boolean isObjectNullOrEmpty(Object value) {
            // 参考org.springframework.util.ObjectUtils.isEmpty(java.lang.Object)
            if (value == null) {
                return true;
            }
            if (isPrimitive(value.getClass())) {
                return false;
            }
            if (value instanceof Optional) {
                return !((Optional) value).isPresent();
            }
            if (value instanceof CharSequence) {
                return ((CharSequence) value).length() == 0;
            }
            if (value instanceof Collection) {
                return ((Collection) value).isEmpty();
            }
            if (value instanceof Map) {
                return ((Map) value).isEmpty();
            }
            if (value.getClass().isArray()) {
                return Array.getLength(value) == 0;
            }
            if (value.getClass().getPackage().getName().startsWith("java.math")) {
                //能够转换成math包下的类对象说明有值
                return false;
            }
            return isEmptyJavaBean(value);
        }

        private static boolean isEmptyJavaBean(Object obj) {
            Class<?> aClass = obj.getClass();
            Field[] fields = aClass.getDeclaredFields();
            boolean flag = true;
            for (Field field : fields) {
                boolean hasChangeAccessFlag = false;
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                    hasChangeAccessFlag = true;
                }
                Object fieldValue = null;
                String fieldName = field.getName();
                if (EXCLUDE_JAVA_BEAN_FILED.equals(fieldName)) {
                    // 忽略序列号字段
                    continue;
                }
                try {
                    fieldValue = field.get(obj);
                } catch (IllegalAccessException e) {
                    // 实际上前面已经确保了属性可以访问，所以不会抛该异常
                    log.error("==> get field err:", e);
                }
                if (hasChangeAccessFlag) {
                    field.setAccessible(false);
                }
                // 递归判断字段是否为空值，有任意一个字段有值则跳出循环
                if (!isObjectNullOrEmpty(fieldValue)) {
                    flag = false;
                    break;
                }
            }
            return flag;
        }


        /**
         * 确定指定的 Class 对象是否表示原始类型
         *
         * @param aClass
         * @return
         */
        private static boolean isPrimitive(Class<?> aClass) {
            if (aClass.isPrimitive()) {
                return true;
            }
            try {
                return ((Class<?>) aClass.getField("TYPE").get(null)).isPrimitive();
            } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
                return false;
            }

        }


    }


}


